'''
Created on 21 Nov 2009

@author: ralf
'''


from google.protobuf.message import Message
from google.protobuf.descriptor import Descriptor, FieldDescriptor
from google.protobuf.reflection import GeneratedProtocolMessageType

from PyQt4 import QtCore, QtGui
import base64
from exceptions import Exception

TYPE_MAP = {}
for name, value in FieldDescriptor.__dict__.items():
    if name.startswith("TYPE_"):
        TYPE_MAP[value] = name[5:].lower()

class EnumValue(object):
    def __init__(self, index=0, values=[]):
        self.index = index
        self.values = values
        
    def value(self):
        return self.values[self.index]
    
    
class CustomItemDelegate(QtGui.QItemDelegate):
    def createEditor(self, parent, option, index):
        data = index.data().toPyObject()
        if isinstance(data, EnumValue):
            editor = QtGui.QComboBox(parent)
            for value in data.values:
                editor.insertItem(editor.count(), value)
            return editor
        else:
            return super(CustomItemDelegate, self).createEditor(parent, option, index)
    
    def setEditorData(self, editor, index):
        data = index.data().toPyObject()
        if isinstance(data, EnumValue):
            editor.setCurrentIndex(data.index)
        else:
            super(CustomItemDelegate, self).setEditorData(self, editor, index)
            
    def setModelData(self, editor, model, index):
        data = index.data().toPyObject()
        if isinstance(data, EnumValue):
            model.setData(index, editor.currentIndex())
        else:
            super(CustomItemDelegate, self).setModelData(self, editor, model, index)
            
    def paint(self, painter, option, index):
        data = index.data().toPyObject()
        if isinstance(data, EnumValue):
            self.drawDisplay(painter, option, option.rect, data.value())
            self.drawFocus(painter, option, option.rect);
        else:
            super(CustomItemDelegate, self).paint(painter, option, index)
    
class EnumItemCreator(QtGui.QItemEditorCreatorBase):
    def valuePropertyName(self):
        return "checkable"
    
    def createWidget(self, parent):
        return QtGui.QCheckBox(parent)
    
ROOT_DESCRIPTOR = FieldDescriptor(
      name='root', full_name='root', index=0,
      number=5, type=12, cpp_type=9, label=1,
      default_value="",
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      options=None)
      
class TreeItem(object):
    def __init__(self, data, descriptor=None, parent=None, model=None, interpretation=0):
        if descriptor is None:
            descriptor = ROOT_DESCRIPTOR
            
        self.itemParent = parent
        self.descriptor = descriptor
        self.itemData = data
        self.interpretation = interpretation
        self.model = model
        self.error = None
        
        
        self.fields = []
        if self.is_message():
            message = self.get_message()
            if message is not None:
                for descriptor, value in self.get_message().ListFields():
                    if descriptor.label == FieldDescriptor.LABEL_REPEATED:
                        for item in value:
                            self.fields.append(TreeItem(item, descriptor, self, model))
                    else:
                        self.fields.append(TreeItem(value, descriptor, self, model))

    def child(self, row):
        return self.fields[row]

    def childCount(self):
        return len(self.fields)
    
    def is_message(self):
        return self.descriptor.type == FieldDescriptor.TYPE_MESSAGE or self.interpretation > 1
    
    def get_message(self):
        if self.descriptor is None or self.descriptor.type == FieldDescriptor.TYPE_MESSAGE:
            return self.itemData
        elif self.interpretation > 1:
            class TemporaryMessage(Message):
                __metaclass__ = GeneratedProtocolMessageType
                
                DESCRIPTOR = self.model.descriptors[self.interpretation - 2]
            
            message = TemporaryMessage()
            try:
                message.ParseFromString(self.itemData)
                return message
            except Exception, e:
                self.error = str(e)
                return None
    
    def is_bytes(self):
        return self.descriptor.type == FieldDescriptor.TYPE_BYTES
    
    def data(self, column):        
        if column == 0:
            return self.descriptor.name
        elif column == 1:
            type = self.descriptor.type
            if self.is_bytes():
                return EnumValue(self.interpretation, ['base64', 'hex'] + [descriptor.full_name for descriptor in self.model.descriptors])
            elif self.is_message():
                return self.descriptor.message_type.full_name
            else:
                return TYPE_MAP[type]
                
        elif column == 2:
            if self.is_message():
                return self.error
            elif self.is_bytes():
                if self.interpretation == 0:
                    return self.itemData.encode("base64")[:-1]
                else:
                    return self.itemData.encode("hex")
                
            else:
                return self.itemData
            
        return None

    def parent(self):
        return self.itemParent
    
    def index_of(self, child):
        return self.fields.index(child)
        
    def row(self):
        if self.parent:
            return self.itemParent.index_of(self)

        return 0
    
    def isEditable(self, column):
        if column == 1 and self.is_bytes():
            return True
        else:
            return False
    
    def set_interpretation(self, interpretation):
        self.interpretation = interpretation
        self.__init__(self.itemData, self.descriptor, self.itemParent, self.model, interpretation)
        
        
    def setData(self, column, value):
        if column == 1 and self.is_bytes():
            self.set_interpretation(value)
            return True
        else:
            return False

class RootItem(object):
    def __init__(self):
        self.item = None
        
    def row(self):
        return 0
    
    def isEditable(self, column):
        return False
    
    def index_of(self, child):
        return 0
    
    def data(self, column):
        return ('Field', 'Type', 'Value')[column]
    
    def child(self, row):
        return self.item

    def childCount(self):
        if self.item:
            return 1
        else:
            return 0
    
class TreeModel(QtCore.QAbstractItemModel):
    def __init__(self, parent=None):
        super(TreeModel, self).__init__(parent)
        self.descriptors = []
        self.rootItem = RootItem()
        
    def set_descriptors(self, descriptors):
        self.descriptors = descriptors
        
    def set_data(self, data):
        self.layoutAboutToBeChanged.emit()
        self.rootItem.item = TreeItem(data, model=self, parent=self.rootItem)
        self.layoutChanged.emit()
        
        
    def columnCount(self, parent):
        return 3

    def data(self, index, role):
        if not index.isValid():
            return None
        
        if role != QtCore.Qt.DisplayRole and role != QtCore.Qt.EditRole:
            return None

        item = index.internalPointer()

        return item.data(index.column())
    
    def getItem(self, index):
        if index.isValid():
            item = index.internalPointer()
            if item:
                return item

        return self.rootItem
    
    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if role != QtCore.Qt.EditRole:
            return False

        item = self.getItem(index)
        self.layoutAboutToBeChanged.emit()
        result = item.setData(index.column(), value)
        self.layoutChanged.emit()
        #if result:
            #self.dataChanged.emit(index, index)
            #self.layoutChanged.emit()

        return result
    
    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.NoItemFlags
        
        item = self.getItem(index)
        if item.isEditable(index.column()):
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
        else:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self.rootItem.data(section)

        return None

    def index(self, row, column, parent):
        if not self.hasIndex(row, column, parent):
            return QtCore.QModelIndex()

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()
            
        childItem = parentItem.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()

        childItem = index.internalPointer()
        
        #FIXME: This is a workaround for a strange bug. Memory issue perhaps?
        if not type(childItem) == TreeItem:
            return QtCore.QModelIndex()
        
        parentItem = childItem.parent()

        if parentItem == self.rootItem:
            return QtCore.QModelIndex()

        return self.createIndex(parentItem.row(), 0, parentItem)

    def rowCount(self, parent):
        if parent.column() > 0:
            return 0

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()

        return parentItem.childCount()
